package com.xseagull.gateway;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import com.alibaba.fastjson.JSON;

import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class AuthorizeGlobalFilter implements GlobalFilter, Ordered {

	private static final Logger logger = LoggerFactory.getLogger(AuthorizeGlobalFilter.class);

	private static final String KEY = "yVwlsbIrY3q22EnoYYM4nR5zqTmqed05";

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest serverHttpRequest = exchange.getRequest();
		ServerHttpResponse serverHttpResponse = exchange.getResponse();
		//return chain.filter(exchange.mutate().request(serverHttpRequest).response(serverHttpResponse).build());
		String contentType = serverHttpRequest.getHeaders().getFirst("Content-Type");
		if (!StringUtils.isEmpty(contentType) && contentType.startsWith("multipart/form-data")) {
			DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
			ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
				@Override
				public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
					if (body instanceof Flux) {
						Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
						return super.writeWith(fluxBody.map(dataBuffer -> {
							String response = "";
							if (this.getStatusCode() == HttpStatus.NOT_FOUND) {
								response = JSON.toJSONString(ResultData.error(ResultCode.NOT_FOUND));
							} else if (this.getStatusCode() == HttpStatus.METHOD_NOT_ALLOWED) {
								response = JSON.toJSONString(ResultData.error(ResultCode.NOT_SUPPORTED));
							} else if (this.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
								response = JSON.toJSONString(ResultData.error(ResultCode.INTERNAL_SERVER_ERROR));
							} else {
								byte[] content = new byte[dataBuffer.readableByteCount()];
								dataBuffer.read(content);
								DataBufferUtils.release(dataBuffer);

								response = new String(content, Charset.forName("UTF-8"));
							}
							logger.info("response=" + response);
							byte[] uppedContent = response.getBytes();
							return bufferFactory.wrap(uppedContent);
						}));
					}
					return super.writeWith(body);
				}
			};
			return chain.filter(exchange.mutate().request(serverHttpRequest).response(decoratedResponse).build());
		}
		logger.info("uri=" + serverHttpRequest.getURI().toString());
		logger.info("header=" + serverHttpRequest.getHeaders().toString());
		String method = serverHttpRequest.getMethodValue();
		Map<String, String> params = new TreeMap<String, String>();
		String parameter = "";
		if ("POST".equals(method)) {

            parameter = exchange.getAttributeOrDefault("cachedRequestBody", "");
			if (!StringUtils.isEmpty(parameter)) {
				try {
					String[] Str = URLDecoder.decode(parameter, "UTF-8").split("&");
					for (String string : Str) {
						String[] split = string.split("=");
						if (split.length > 1) {
							params.put(split[0], split[1]);
						}
					}
					logger.info("parameter={" + parameter + "}");
				} catch (UnsupportedEncodingException e1) {
					e1.printStackTrace();
				}
			}
		} else {
			MultiValueMap<String, String> multiValueMap = serverHttpRequest.getQueryParams();
			multiValueMap.forEach((k, v) -> {
				params.put(k, v.get(0));
			});
			logger.info("parameter={" + multiValueMap.toString() + "}");
		}

		// 签名
		boolean flag = checkSignature(serverHttpRequest, params);
		if (flag) {
			URI uri = serverHttpRequest.getURI();
			if (uri.getPath().indexOf("/auth") != -1) {
				// 不需要认证
				ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
				DataBuffer bodyDataBuffer = stringBuffer(parameter);
				Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

				request = new ServerHttpRequestDecorator(request) {
					@Override
					public Flux<DataBuffer> getBody() {
						return bodyFlux;
					}
				};
				// 封装request，传给下一级

				DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
				ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
					@Override
					public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
						if (uri.getPath().indexOf("/export") == -1) {
							if (body instanceof Flux) {
								Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
								return super.writeWith(fluxBody.map(dataBuffer -> {
									String response = "";
									if (this.getStatusCode() == HttpStatus.NOT_FOUND) {
										response = JSON.toJSONString(ResultData.error(ResultCode.NOT_FOUND));
									} else if (this.getStatusCode() == HttpStatus.METHOD_NOT_ALLOWED) {
										response = JSON.toJSONString(ResultData.error(ResultCode.NOT_SUPPORTED));
									} else if (this.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
										response = JSON.toJSONString(ResultData.error(ResultCode.INTERNAL_SERVER_ERROR));
									} else {
										byte[] content = new byte[dataBuffer.readableByteCount()];
										dataBuffer.read(content);
										DataBufferUtils.release(dataBuffer);

										response = new String(content, Charset.forName("UTF-8"));
									}
									logger.info("response=" + response);
									byte[] uppedContent = response.getBytes();
									return bufferFactory.wrap(uppedContent);
								}));
							}
						}
						return super.writeWith(body);
					}
				};
				return chain.filter(exchange.mutate().request(request).response(decoratedResponse).build());
			} else {
				// 需要认证
				String authHeader = serverHttpRequest.getHeaders().getFirst(JwtTokenUtil.HEADER);
				if (authHeader != null && authHeader.startsWith(JwtTokenUtil.TOKENHEAD)) {
					final String authToken = authHeader.substring(JwtTokenUtil.TOKENHEAD.length());
					String userId = JwtTokenUtil.getUserIdFromToken(authToken);
					if (!StringUtils.isEmpty(userId)) {
						if (!JwtTokenUtil.validateToken(authToken)) {
							return errorHanle(serverHttpResponse, ResultData.warn(ResultCode.UNAUTHORIZED));
						} else {
							StringBuilder query = new StringBuilder();
							String originalQuery = uri.getRawQuery();
							if (StringUtils.hasText(originalQuery)) {
								query.append(originalQuery);
								if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
									query.append('&');
								}
							}
							query.append("userId").append('=').append(userId);

							URI newUri = UriComponentsBuilder.fromUri(serverHttpRequest.getURI())
									.replaceQuery(query.toString()).build(true).toUri();

							ServerHttpRequest request = serverHttpRequest.mutate().uri(newUri).build();
							DataBuffer bodyDataBuffer = stringBuffer(parameter);
							Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

							request = new ServerHttpRequestDecorator(request) {
								@Override
								public Flux<DataBuffer> getBody() {
									return bodyFlux;
								}
							};

							DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
							ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(
									serverHttpResponse) {
								@Override
								public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
									if (uri.getPath().indexOf("/export") == -1) {
										if (body instanceof Flux) {
											Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
											return super.writeWith(fluxBody.map(dataBuffer -> {
												String response = "";
												if (this.getStatusCode() == HttpStatus.NOT_FOUND) {
													response = JSON.toJSONString(ResultData.error(ResultCode.NOT_FOUND));
												} else if (this.getStatusCode() == HttpStatus.METHOD_NOT_ALLOWED) {
													response = JSON
															.toJSONString(ResultData.error(ResultCode.NOT_SUPPORTED));
												} else if (this.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
													response = JSON.toJSONString(
															ResultData.error(ResultCode.INTERNAL_SERVER_ERROR));
												} else {
													byte[] content = new byte[dataBuffer.readableByteCount()];
													dataBuffer.read(content);
													DataBufferUtils.release(dataBuffer);

													response = new String(content, Charset.forName("UTF-8"));
												}
												logger.info("response=" + response);
												byte[] uppedContent = response.getBytes();
												return bufferFactory.wrap(uppedContent);
											}));
										}
									}
									return super.writeWith(body);
								}
							};

							return chain.filter(exchange.mutate().request(request).response(decoratedResponse).build());

						}
					} else {
						return errorHanle(serverHttpResponse, ResultData.warn(ResultCode.UNAUTHORIZED));
					}
				} else {
					return errorHanle(serverHttpResponse, ResultData.warn(ResultCode.UNAUTHORIZED));
				}
			}
		} else {
			return errorHanle(serverHttpResponse, ResultData.warn(ResultCode.SIGN_ERROR));
		}
	}

	private Mono<Void> errorHanle(ServerHttpResponse serverHttpResponse, ResultData resultData) {
		String response = JSON.toJSONString(resultData);
		logger.info("response=" + response);
		DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(response.getBytes());
		serverHttpResponse.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
		return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer));
	}

	private boolean checkSignature(ServerHttpRequest serverHttpRequest, Map<String, String> params) {
		String sign = serverHttpRequest.getHeaders().getFirst("Sign");
		String timestamp = serverHttpRequest.getHeaders().getFirst("Timestamp");

		if (StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestamp)) {
			return false;
		}
		params.put("timestamp", timestamp);

		StringBuilder builder = new StringBuilder();

		params.forEach((k, v) -> {
			if (!StringUtils.isEmpty(v)) {
				builder.append(k).append("=").append(v).append("&");
			}
		});

		builder.append("key=").append(KEY);

		String value = builder.toString();
		if (!sign.equals(EncryptUtils.md5Encode(value).toUpperCase())) {
			return false;
		}

		return true;
	}
	@Override
	public int getOrder() {
		return -20;
	}

	private DataBuffer stringBuffer(String value) {
		if (!StringUtils.isEmpty(value)) {
			byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
			NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
			DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
			buffer.write(bytes);
			return buffer;
		} else {
			byte[] bytes = new byte[0];
			NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
			DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(0);
			buffer.write(bytes);
			return buffer;
		}
	}
}
